﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SharpSoft.WPF.Animations
{
    /// <summary>
    /// 动画相关的扩展
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// 为<see cref="Shape"/>创建一个绘制过程的动画
        /// </summary>
        /// <param name="shape"></param>
        /// <param name="duration"></param>
        /// <returns></returns>
        public static Storyboard CreateShapeDrawAnimation(this Shape shape, TimeSpan duration)
        {
            Storyboard storyboard = new Storyboard();

            var length = GetGeometryLength(shape.RenderedGeometry);
            var olddasharray = shape.StrokeDashArray;//记录旧的dasharray 

            var actLength = length / shape.StrokeThickness;//注意Dash的长度单位与线条宽度有关

            var newdasharray = new DoubleCollection(new double[] { actLength, });
            shape.StrokeDashArray = newdasharray;
            var ani = new DoubleAnimation() { From = actLength, To = 0, Duration = duration };
            Storyboard.SetTarget(ani, shape);
            Storyboard.SetTargetProperty(ani, new PropertyPath(Path.StrokeDashOffsetProperty));
            storyboard.Completed += (s, e) =>
            {
                shape.StrokeDashArray = olddasharray;
            };
            storyboard.Children.Add(ani);

            return storyboard;
        }
        public static Storyboard CreateShapeDrawAnimationAndRun(this Shape shape, TimeSpan duration)
        {
            var sb = shape.CreateShapeDrawAnimation(duration);
            sb.Begin();
            return sb;
        }
        /// <summary>
        /// 为<see cref="Shape"/>创建一个线条流动的画面(永远重复)
        /// </summary>
        /// <param name="shape"></param>
        /// <param name="dashLength">虚线线段长度</param>
        /// <param name="duration"></param>
        /// <returns></returns>
        public static Storyboard CreateShapeFlowAnimation(this Shape shape, TimeSpan duration)
        {
            Storyboard storyboard = new Storyboard() { RepeatBehavior = RepeatBehavior.Forever };
            var dashArray = shape.StrokeDashArray;
            if (dashArray == null || dashArray.Count == 0)
            {
                throw new Exception("必须为Shape指定DashArray才允许创建流动动画。");
            }
            double l = 0d;
            if (dashArray.Count < 2)
            {
                l = dashArray[0] * 2;
            }
            else
            {
                foreach (var item in dashArray)
                {
                    l += item;
                }
            }

            var ani = new DoubleAnimation() { From = 0, To = l, Duration = duration };
            Storyboard.SetTarget(ani, shape);
            Storyboard.SetTargetProperty(ani, new PropertyPath(Path.StrokeDashOffsetProperty));
            //storyboard.Completed += (s, e) =>
            //{ 
            //};
            storyboard.Children.Add(ani);

            return storyboard;
        }

        public static Storyboard CreateShapeFlowAnimationAndRun(this Shape shape, TimeSpan duration)
        {
            var sb = shape.CreateShapeFlowAnimation(duration);
            sb.Begin();
            return sb;
        }
        /// <summary>
        /// 获取几何图形的线条长度
        /// </summary>
        /// <param name="geo"></param>
        /// <returns></returns>
        private static double GetGeometryLength(this Geometry geo)
        {
            PathGeometry path = geo.GetFlattenedPathGeometry();

            double length = 0.0;

            foreach (PathFigure pf in path.Figures)
            {
                Point start = pf.StartPoint;

                foreach (PolyLineSegment seg in pf.Segments)
                {
                    foreach (Point point in seg.Points)
                    {
                        length += (point - start).Length;
                        start = point;
                    }
                }
            }
            return length;
        }

        /// <summary>
        /// 创建移动到另一个控件的动画(使用控件的RenderTransform属性创建动画，使用此方法会影响控件原有的变换)
        /// </summary>
        /// <param name="element"></param>
        /// <param name="target"></param>
        /// <param name="duration"></param>
        /// <param name="customAni">自定义DoubleAnimation，参数为DoubleAnimation的目标值（ToProperty）</param>
        /// <returns></returns>
        public static Storyboard CreateMoveToTargetAnimation(this FrameworkElement element, FrameworkElement target, TimeSpan duration, Func<double, DoubleAnimationBase> customAni = null)
        {
            if (element == null)
            {
                throw new ArgumentNullException(nameof(element));
            }
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            } 

            var pc1 = new Point(element.ActualWidth *element.RenderTransformOrigin.X, element.ActualHeight * element.RenderTransformOrigin.Y);
            var pc2 = target.TranslatePoint(new Point(target.ActualWidth * element.RenderTransformOrigin.X, target.ActualHeight * element.RenderTransformOrigin.Y), element);
            var tv = pc2 - pc1; //需要平移的量
            //需要缩放的量
            var sx = target.ActualWidth / element.ActualWidth;
            var sy = target.ActualHeight / element.Height;
            Func<double, DoubleAnimationBase> createAni = (to) =>
            {
                if (customAni != null)
                {
                    return customAni.Invoke(to);
                }
                return new DoubleAnimation() { To = to };
            };
            NameScope.SetNameScope(element, new NameScope());
            TransformGroup tg = new TransformGroup();
            TranslateTransform translateTransform = new TranslateTransform(0, 0);
            ScaleTransform scaleTransform = new ScaleTransform(1, 1) { CenterX = 0.5, CenterY = 0.5 };
            tg.Children.Add(scaleTransform);//注意变换的顺序必须是先缩放后平移
            tg.Children.Add(translateTransform);
            element.RenderTransform = tg;
            element.RegisterName("translateTransform", translateTransform);
            element.RegisterName("scaleTransform", scaleTransform);

            //平移X
            var translateXAni = createAni(tv.X);
            translateXAni.Duration = duration;
            Storyboard.SetTargetName(translateXAni, "translateTransform");
            Storyboard.SetTargetProperty(translateXAni, new PropertyPath(TranslateTransform.XProperty));
            //平移Y
            var translateYAni = createAni(tv.Y);
            translateYAni.Duration = duration;
            Storyboard.SetTargetName(translateYAni, "translateTransform");
            Storyboard.SetTargetProperty(translateYAni, new PropertyPath(TranslateTransform.YProperty));

            //缩放X
            var scaleXAni = createAni(sx);
            scaleXAni.Duration = duration;
            Storyboard.SetTargetName(scaleXAni, "scaleTransform");
            Storyboard.SetTargetProperty(scaleXAni, new PropertyPath(ScaleTransform.ScaleXProperty));
            //缩放Y
            var scaleYAni = createAni(sy);
            scaleYAni.Duration = duration;
            Storyboard.SetTargetName(scaleYAni, "scaleTransform");
            Storyboard.SetTargetProperty(scaleYAni, new PropertyPath(ScaleTransform.ScaleYProperty));

            Storyboard sb = new Storyboard() { Duration = duration, FillBehavior = FillBehavior.Stop };
            sb.Children.Add(translateXAni);
            sb.Children.Add(translateYAni);
            sb.Children.Add(scaleXAni);
            sb.Children.Add(scaleYAni);
            return sb;
        }
        public static Storyboard CreateMoveToTargetAnimationAndRun(this FrameworkElement element, FrameworkElement target, TimeSpan duration, Func<double, DoubleAnimationBase> customAni = null)
        {
            var sb = CreateMoveToTargetAnimation(element, target, duration, customAni);
            sb.Begin(element);
            return sb;
        }
        /// <summary>
        /// 创建文字动画（有BUG暂不要使用）
        /// </summary>
        /// <param name="textBlock"></param>
        /// <param name="effectsType"></param>
        /// <param name="duration"></param>
        /// <param name="fromVector"></param>
        /// <param name="toVector"></param>
        /// <returns></returns>
        [Obsolete("有BUG暂不要使用", true)]
        public static Storyboard CreateTextAnimation(this TextBlock textBlock,
           TextAnimationType effectsType, TimeSpan duration, Vector fromVector, Vector toVector)
        {

            var txt = textBlock.Text;

            if (string.IsNullOrEmpty(txt))
            {
                return new Storyboard();
            }
            NameScope.SetNameScope(textBlock, new NameScope());//为textblock创建新的命名域
            TextEffect te = new TextEffect() { PositionCount = 1 };
            Transform transform = null;


            var durone = duration / txt.Length;
            //单个文字动画
            DoubleAnimation dax = new DoubleAnimation(fromVector.X, toVector.X, durone) { RepeatBehavior = new RepeatBehavior(txt.Length) };//有多少个文字就重复多少次
            DoubleAnimation day = new DoubleAnimation(fromVector.Y, toVector.Y, durone) { RepeatBehavior = new RepeatBehavior(txt.Length) };
            switch (effectsType)
            {
                case TextAnimationType.Scale:
                    transform = new ScaleTransform() { CenterX = 10, CenterY = 10, ScaleX = 1, ScaleY = 1 };
                    Storyboard.SetTargetProperty(dax, new PropertyPath(ScaleTransform.ScaleXProperty));
                    Storyboard.SetTargetProperty(day, new PropertyPath(ScaleTransform.ScaleYProperty));
                    break;
                case TextAnimationType.Skew:
                    transform = new SkewTransform() { CenterX = 10, CenterY = 10, AngleX = 0, AngleY = 0 };
                    Storyboard.SetTargetProperty(dax, new PropertyPath(SkewTransform.AngleXProperty));
                    Storyboard.SetTargetProperty(day, new PropertyPath(SkewTransform.AngleYProperty));
                    break;
                case TextAnimationType.Translate:
                    transform = new TranslateTransform() { X = 0, Y = 0 };
                    Storyboard.SetTargetProperty(dax, new PropertyPath(TranslateTransform.XProperty));
                    Storyboard.SetTargetProperty(day, new PropertyPath(TranslateTransform.YProperty));
                    break;
                default:
                    break;
            }
            te.Transform = transform;
            textBlock.TextEffects.Add(te);
            textBlock.RegisterName("transform", transform);
            textBlock.RegisterName("effect", te);
            Storyboard.SetTargetName(dax, "transform");
            Storyboard.SetTargetName(day, "transform");

            Int32AnimationUsingKeyFrames txtIndexAni = new Int32AnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames centerXAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames centerYAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            var p = 1d / (double)txt.Length;

            var pointer = textBlock.ContentStart;//文字游标，需要根据游标来确定每个文字的位置
            //TODO:BUG     
            //*****20191110****TextBlock在读取游标位置（如ContentStart）后TextEffect的动画失效
            for (int i = 0; i < txt.Length; i++)
            {
                var kt = KeyTime.FromPercent(p * i);
                DiscreteInt32KeyFrame kf = new DiscreteInt32KeyFrame(i, kt);
                txtIndexAni.KeyFrames.Add(kf);

                var trect = pointer.GetCharacterRect(System.Windows.Documents.LogicalDirection.Forward);

                DiscreteDoubleKeyFrame kfx = new DiscreteDoubleKeyFrame(trect.X + trect.Width / 2, kt);
                DiscreteDoubleKeyFrame kfy = new DiscreteDoubleKeyFrame(trect.Y + trect.Height / 2, kt);
                centerXAni.KeyFrames.Add(kfx);
                centerYAni.KeyFrames.Add(kfy);
                pointer = pointer.GetPositionAtOffset(1, System.Windows.Documents.LogicalDirection.Forward);

            }
            Storyboard.SetTargetName(txtIndexAni, "effect");
            Storyboard.SetTargetProperty(txtIndexAni, new PropertyPath(TextEffect.PositionStartProperty));

            Storyboard storyboard = new Storyboard() { FillBehavior = FillBehavior.Stop };
            storyboard.Children.Add(dax);
            storyboard.Children.Add(day);
            storyboard.Children.Add(txtIndexAni);

            switch (effectsType)
            {
                case TextAnimationType.Scale:
                    Storyboard.SetTargetName(centerXAni, "transform");
                    Storyboard.SetTargetName(centerYAni, "transform");
                    Storyboard.SetTargetProperty(centerXAni, new PropertyPath(ScaleTransform.CenterXProperty));
                    Storyboard.SetTargetProperty(centerYAni, new PropertyPath(ScaleTransform.CenterYProperty));
                    storyboard.Children.Add(centerXAni);
                    storyboard.Children.Add(centerYAni);
                    break;
                case TextAnimationType.Skew:
                    Storyboard.SetTargetName(centerXAni, "transform");
                    Storyboard.SetTargetName(centerYAni, "transform");
                    Storyboard.SetTargetProperty(centerXAni, new PropertyPath(SkewTransform.CenterXProperty));
                    Storyboard.SetTargetProperty(centerYAni, new PropertyPath(SkewTransform.CenterYProperty));
                    storyboard.Children.Add(centerXAni);
                    storyboard.Children.Add(centerYAni);
                    break;
                case TextAnimationType.Translate:
                default:
                    break;
            }


            storyboard.Completed += (s, e) =>
            {
                textBlock.TextEffects.Remove(te);
            };
            return storyboard;
        }


        /// <summary>
        /// 创建复杂数据的关键帧动画
        /// </summary>
        /// <param name="element">要生成动画的元素</param>
        /// <param name="prop">要应用动画的属性</param>
        /// <param name="data">动画用到的全部数据</param>
        /// <param name="framerate">帧率</param>
        /// <returns></returns>
        public static Storyboard CreateObjectKeyFramesAnimation(this UIElement element, PropertyPath prop, object[] data, int framerate)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data");
            }
            if (framerate < 1 || framerate > 100)
            {
                throw new Exception("帧率必须大于0且小于100.");
            }
            ObjectAnimationUsingKeyFrames ani = new ObjectAnimationUsingKeyFrames();
            TimeSpan ide = new TimeSpan(0);
            var add = TimeSpan.FromSeconds(1) / framerate;
            foreach (var item in data)
            {
                var kf = new DiscreteObjectKeyFrame(item, KeyTime.FromTimeSpan(ide));
                ide += add;
                ani.KeyFrames.Add(kf);
            }
            Storyboard.SetTarget(ani, element);
            Storyboard.SetTargetProperty(ani, prop);
            Storyboard sb = new Storyboard();
            sb.Children.Add(ani);
            return sb;
        }


        public static Storyboard CreateTransformAnimation(this UIElement element, TimeSpan duration, Dictionary<double, TransformData> frames)
        {
            TransformGroup tg = new TransformGroup();
            ScaleTransform scaleTransform = new ScaleTransform();
            RotateTransform rotateTransform = new RotateTransform();
            TranslateTransform translateTransform = new TranslateTransform();
            tg.Children.Add(scaleTransform);
            tg.Children.Add(rotateTransform);
            tg.Children.Add(translateTransform);
            element.RenderTransform = tg;

            DoubleAnimationUsingKeyFrames scalXAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames scalYAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames angleAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames offsetXAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            DoubleAnimationUsingKeyFrames offsetYAni = new DoubleAnimationUsingKeyFrames() { Duration = duration };
            Storyboard.SetTarget(scalXAni, element);
            Storyboard.SetTarget(scalYAni, element);
            Storyboard.SetTarget(angleAni, element);
            Storyboard.SetTarget(offsetXAni, element);
            Storyboard.SetTarget(offsetYAni, element);
            Storyboard.SetTargetProperty(scalXAni, new PropertyPath("(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
            Storyboard.SetTargetProperty(scalYAni, new PropertyPath("(RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
            Storyboard.SetTargetProperty(angleAni, new PropertyPath("(RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"));
            Storyboard.SetTargetProperty(offsetXAni, new PropertyPath("(RenderTransform).(TransformGroup.Children)[2].(TranslateTransform.X)"));
            Storyboard.SetTargetProperty(offsetYAni, new PropertyPath("(RenderTransform).(TransformGroup.Children)[2].(TranslateTransform.Y)"));

            foreach (var item in frames)
            {
                scalXAni.KeyFrames.Add(new LinearDoubleKeyFrame(item.Value.ScaleX, KeyTime.FromPercent(item.Key)));
                scalYAni.KeyFrames.Add(new LinearDoubleKeyFrame(item.Value.ScaleY, KeyTime.FromPercent(item.Key)));
                angleAni.KeyFrames.Add(new LinearDoubleKeyFrame(item.Value.Angle, KeyTime.FromPercent(item.Key)));
                offsetXAni.KeyFrames.Add(new LinearDoubleKeyFrame(item.Value.OffsetX, KeyTime.FromPercent(item.Key)));
                offsetYAni.KeyFrames.Add(new LinearDoubleKeyFrame(item.Value.OffsetY, KeyTime.FromPercent(item.Key)));
            }
            Storyboard storyboard = new Storyboard() { FillBehavior = FillBehavior.Stop };
            storyboard.Children.Add(scalXAni);
            storyboard.Children.Add(scalYAni);
            storyboard.Children.Add(angleAni);
            storyboard.Children.Add(offsetXAni);
            storyboard.Children.Add(offsetYAni);
            return storyboard;
        }
    }
    /// <summary>
    /// 定义常规变换的数据
    /// </summary>
    public struct TransformData
    {
        public double ScaleX { get; set; }

        public double ScaleY { get; set; }

        public double Angle { get; set; }

        public double OffsetX { get; set; }

        public double OffsetY { get; set; }


        public TransformGroup GetTransformGroup()
        {
            TransformGroup tg = new TransformGroup();
            ScaleTransform scaleTransform = new ScaleTransform() { ScaleX = ScaleX, ScaleY = ScaleY };
            RotateTransform rotateTransform = new RotateTransform() { Angle = Angle };
            TranslateTransform translateTransform = new TranslateTransform() { X = OffsetX, Y = OffsetY };
            tg.Children.Add(scaleTransform);
            tg.Children.Add(rotateTransform);
            tg.Children.Add(translateTransform);
            return tg;
        }
    }

    /// <summary>
    /// 文字动画类型
    /// </summary>
    public enum TextAnimationType
    {
        Scale,
        Skew,
        Translate
    }
}
